/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 *
 */
public final class Numbers {
    
    private Numbers() {}

    public static BigDecimal min(BigDecimal value1, BigDecimal value2) {
        Checks.verifyNotNull(value1, "value1");
        Checks.verifyNotNull(value2, "value2");
        return (value1.compareTo(value2) > 0) ? value2 : value1;
    }

    public static BigDecimal max(BigDecimal value1, BigDecimal value2) {
        Checks.verifyNotNull(value1, "value1");
        Checks.verifyNotNull(value2, "value2");
        return (value1.compareTo(value2) > 0) ? value1 : value2;
    }

    /**
     * 返回去掉不影响数值大小的0后的BigDecimal
     * @param value 数值
     * @return 返回去掉不影响数值大小的0后的BigDecimal
     */
    public static BigDecimal trimZero(BigDecimal value) {
        if (value == null) {
            return null;
        }
        return new BigDecimal(trimZeroString(value));
    }
    
    /**
     * 将数值转为字符串形式并去掉不影响数值大小的0
     * @param value 数值
     * @return 返回去掉不影响数值大小的0后的字符串
     */
    public static String trimZeroString(BigDecimal value) {
        if (value == null) {
            return "";
        }
        String str = value.toPlainString();
        int dotIndex = str.indexOf('.');
        if (dotIndex > 0) {
            int i = str.length() - 1;
            while (i > dotIndex) {
                if (str.charAt(i) != '0') {
                    break;
                }
                i--;
            }
            if (i == dotIndex) {
                i--;
            }
            str = str.substring(0, i + 1);
        }
        return str;
    }

    /**
     * 获取指定数值的负数值，如果数值为 null 则返回 null
     * @param value 数值
     * @return 返回指定值的负数值，如果 value 为 null 则返回 null
     */
    public static BigDecimal negate(BigDecimal value) {
        return value == null ? null : value.negate();
    }

    /**
     * 如果value为null返回BigDecimal.ZERO，否则返回value
     * @param value 数值
     * @return 返回数值或BigDecimal.ZERO
     */
    public static BigDecimal nullToZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }

    /**
     * 如果value为null返回0，否则返回value
     * @param value 数值
     * @return 如果数值或0
     */
    public static long nullToZero(Long value) {
        return value == null ? 0 : value;
    }

    /**
     * 如果value为null返回0，否则返回value
     * @param value 数值
     * @return 如果数值或0
     */
    public static int nullToZero(Integer value) {
        return value == null ? 0 : value;
    }

    /**
     * 如果value为null返回0，否则返回value
     * @param value 数值
     * @return 如果数值或0
     */
    public static short nullToZero(Short value) {
        return value == null ? 0 : value;
    }

    /**
     * 如果value为null返回0，否则返回value
     * @param value 数值
     * @return 如果数值或0
     */
    public static double nullToZero(Double value) {
        return value == null ? 0 : value;
    }

    /**
     * 如果value为null返回0，否则返回value
     * @param value 数值
     * @return 如果数值或0
     */
    public static float nullToZero(Float value) {
        return value == null ? 0 : value;
    }

    /**
     * 判断value1和value2的值是否相等，null的值仅等于null
     * @param value1 数值1
     * @param value2 数值2
     * @return 如果相等就返回true，否则返回false
     */
    public static boolean eqValue(BigDecimal value1, BigDecimal value2) {
        if (value1 == null) {
            return value2 != null;
        }
        return value2 != null && value1.compareTo(value2) == 0;
    }

    /**
     * 返回指定数值的整数部分
     * @param value 数值
     * @return 返回指定数值的整数部分
     */
    public static long integralPart(BigDecimal value) {
        return value.setScale(0, RoundingMode.FLOOR).longValue();
    }

    /**
     * 返回指定数值的小数部分
     * @param value 数值
     * @return 返回指定数值的小数部分
     */
    public static long decimalPart(BigDecimal value) {
        BigDecimal integralPart = value.setScale(0, RoundingMode.FLOOR);
        BigDecimal decimal = value.subtract(integralPart);
        return decimal.movePointRight(decimal.scale()).longValue();
    }
    
    /**
     * 返回value的低bits位
     * @param value 数值
     * @param bits 位数
     * @return 返回value的低bits位
     */
    public static int low(int value, int bits) {
        if (bits <= 0) {
            return 0;
        }
        BigDecimal v = BigDecimal.valueOf(value);
        if (v.precision() <= bits) {
            return value;
        }
        v = v.movePointLeft(bits);
        v = v.subtract(BigDecimal.valueOf(v.intValue()));
        v = v.movePointRight(bits);
        return v.intValue();
    }

    /**
     * 返回value的高bits位
     * @param value 数值
     * @param bits 位数
     * @return 返回value的高bits位
     */
    public static int high(int value, int bits) {
        if (bits <= 0) {
            return 0;
        }
        BigDecimal v = BigDecimal.valueOf(value);
        int i = v.precision() - bits;
        if (i <= 0) {
            return value;
        }
        return v.movePointLeft(i).intValue();
    }
    
    /**
     * 返回指定位的值
     * @param value 数值
     * @param bitIndex 正数表示小数点前的第N位，负数表示小数点后的第N位。如：3表求小数点前的第3位，-1表示小数点后的第1位
     * @return 返回指定位的值
     */
    public static int bit(BigDecimal value, int bitIndex) {
        if (bitIndex == 0) {
            return 0;
        }
        String str = trimZeroString(value.abs());
        int dotIndex = str.indexOf('.');
        int i = bitIndex;
        if (dotIndex > 0) {
            i = dotIndex - bitIndex;
        } else {
            i = str.length() - i;
        }
        if (i < 0 || i >= str.length()) {
            return 0;
        }
        return Integer.parseInt(String.valueOf(str.charAt(i)));
    }
    
    /**
     * 将指定数值bits位向下对齐5，如：12.586第2位向下对齐的结果为12.55
     * @param value 数值
     * @param bitIndex 正数表示小数点前的第N位，负数表示小数点后的第N位。如：3表求小数点前的第3位，-1表示小数点后的第1位
     * @return 返回向下对齐的数值
     */
    public static BigDecimal floorOfFive(BigDecimal value, int bitIndex) {
        String str = trimZeroString(value.abs());
        int dotIndex = str.indexOf('.');
    
        if (bitIndex == 0 && dotIndex < 0) {
            return value;
        }
    
        BigDecimal newValue;
        if (bitIndex == 0) {
            newValue = new BigDecimal(str.substring(0, dotIndex));
        } else {
            if (dotIndex < 0) {
                dotIndex = str.length();
            }
        
            int i = dotIndex - bitIndex;
            if (i < 0) {
                return BigDecimal.ZERO;
            }
        
            if (i >= str.length()) {
                return value;
            }
        
            StringBuilder buf = new StringBuilder();
            if (i > 0) {
                buf.append(str.substring(0, i));
            }
            char c = str.charAt(i);
            if (c > '5') {
                buf.append("5");
            } else {
                buf.append("0");
            }
            for (i++; i < dotIndex; i++) {
                buf.append("0");
            }
            newValue = new BigDecimal(buf.toString());
        }
    
        if (value.signum() < 0) {
            newValue = newValue.negate();
        }
        return trimZero(newValue);
    }
    
    /**
     * 从最后一位向前逐位向下对齐5，直到stopBits位（包含）为止，并返回每次对齐的结果
     * @param value 数值
     * @param stopBitIndex 停止向下对齐的位
     * @return 返回每次对齐的结果
     */
    public static List<BigDecimal> floorOfFiveByBitwise(BigDecimal value, int stopBitIndex) {
        List<BigDecimal> resultList = new ArrayList<>();
        
        value = trimZero(value);
        int scale = value.scale();
        while (scale != 0 && scale >= -stopBitIndex) {
            value = floorOfFive(value, -scale);
            resultList.add(value);
            scale = value.scale();
        }
        
        if (stopBitIndex > 0) {
            value = trimZero(value.movePointLeft(stopBitIndex));
            scale = value.scale();
            if (scale > 0) {
                int bit = bit(value, 1);
                while (scale != 0 || bit != 0) {
                    value = floorOfFive(value, -scale);
                    BigDecimal newValue = value.movePointRight(stopBitIndex);
                    resultList.add(newValue);
                    scale = value.scale();
                    bit = bit(newValue, 1);
                }
            }
        }
        
        return resultList;
    }
    
}
